-
Notifications
You must be signed in to change notification settings - Fork 158
refactor(backport): prepare the client interfaces used by the observe… #4271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthroughRefactors zetaclient to replace a monolithic interfaces layer with per-chain Client interfaces and repositories. Updates observers/signers across Bitcoin, EVM, Solana, Sui, and TON to depend on new clients, adjusts constructors and method signatures, migrates Solana logic to a repo wrapper, and replaces Zetacore GetAllOutboundTrackerByChain with GetOutboundTrackers. Updates mocks/tests and orchestrator wiring accordingly. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Orch as Orchestrator
participant BTC as bitcoin.Client
participant EVM as evm.Client
participant SOL as solana.Client
participant SUI as sui.Client
participant TON as ton.Client
participant ZC as ZetacoreClient
Orch->>BTC: create standardBitcoinClient
Orch->>EVM: create standardEvmClient
Orch->>SOL: create standardSolanaClient
Orch->>SUI: create standardSuiClient
Orch->>TON: create standardTonClient
note over Orch,SOL: Solana health via repo
Orch->>SOL: repo.New(solanaClient).HealthCheck(ctx)
Orch->>ZC: GetOutboundTrackers(ctx, chainID)
ZC-->>Orch: []OutboundTracker (ascending)
sequenceDiagram
autonumber
participant Obs as Solana Observer
participant Repo as SolanaRepo
participant RPC as SolanaClient
Obs->>Repo: GetFirstSignatureForAddress(ctx, addr, limit)
Repo->>RPC: GetSignaturesForAddressWithOpts(...)
RPC-->>Repo: []*TransactionSignature
Repo-->>Obs: first Signature
Obs->>Repo: GetTransaction(ctx, sig)
Repo->>RPC: GetTransaction(sig, opts)
RPC-->>Repo: GetTransactionResult
Repo-->>Obs: GetTransactionResult
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
265c3c9 to
4234313
Compare
eef74eb to
8f824b5
Compare
4234313 to
e9998df
Compare
8f824b5 to
c027ffd
Compare
e9998df to
61fea73
Compare
c027ffd to
a2f0013
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
zetaclient/chains/bitcoin/signer/signer_keysign_test.go (1)
145-163: Fix P2WPKH scriptCode, hashType usage, and error handling in getTSSTX
- Build the P2PKH scriptCode (OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG) from
tssSigner.PubKey().Bytes(true)and pass it totxscript.CalcWitnessSigHashinstead of the P2WPKH output script.- Use the
hashTypeparameter (not a hard-codedtxscript.SigHashAll) when callingCalcWitnessSigHash.- Check
errimmediately aftertssSigner.Sign—before slicingsig65B—to avoid nil-slice panics.- Optional: normalize S to low-S if the TSS doesn’t guarantee it.
zetaclient/chains/solana/signer/outbound_tracker_reporter.go (1)
61-67: Potential nil dereference on tx.Metarpc.GetTransaction may return a result with nil Meta; accessing tx.Meta.Err would panic. Guard both tx and tx.Meta before use.
- tx, err := signer.solanaClient.GetTransaction(ctx, txSig, &rpc.GetTransactionOpts{ + tx, err := signer.solanaClient.GetTransaction(ctx, txSig, &rpc.GetTransactionOpts{ Commitment: rpc.CommitmentConfirmed, }) if err != nil { continue } // exit goroutine if tx failed. - if tx.Meta.Err != nil { + if tx == nil || tx.Meta == nil { + continue + } + if tx.Meta.Err != nil { ... }Also applies to: 71-82
zetaclient/zetacore/client_crosschain.go (1)
27-49: Missing pagination risks dropping recent trackersWe cap this query at 2 000 results with
Reversehardcoded tofalse, yet the comment advertises “returns all”. Once a chain accumulates more than 2 000 outbound trackers, this will only ever return the earliest nonces. Callers such asEVM.scheduleCCTXdepend on recent tracker membership to tune scheduling intervals; without the latest entries, that logic will quietly degrade. Previously the order parameter let us flip the store iterator to grab the newest slice—this refactor removes that escape hatch. Please either restore a way to fetch the most recent window (e.g., expose the reverse flag) or paginate until we really do cover the full set; otherwise we’re shipping with a latent data-loss bug.zetaclient/chains/evm/client/client.go (1)
57-66: Validate tx hash instead of silently coercing with HexToHashAvoid accepting malformed hashes; parse with error handling to prevent querying with zero/invalid hashes.
Apply this diff:
- hash := common.HexToHash(txHash) + var hash common.Hash + if err := hash.UnmarshalText([]byte(txHash)); err != nil { + return false, errors.Wrapf(err, "invalid tx hash %q", txHash) + }zetaclient/chains/solana/observer/inbound.go (1)
60-63: Clarify log message“got wrong amount of signatures” reads like an error. Prefer a neutral message (e.g., “found signatures”).
Apply this diff:
- Msg("got wrong amount of signatures") + Msg("found new signatures")zetaclient/orchestrator/bootstrap.go (1)
172-178: Don’t fail Solana bootstrap when the relayer key is absent (optional key).The code returns an error if the relayer key is not found, despite the comment stating it is optional. Treat “not found” as non-fatal and proceed with a nil key.
Apply this diff:
- password := chain.RelayerKeyPassword() - relayerKey, err := keys.LoadRelayerKey(app.Config().GetRelayerKeyPath(), chain.RawChain().Network, password) - if err != nil { - return nil, errors.Wrap(err, "unable to load relayer key") - } + var relayerKey *keys.RelayerKey + password := chain.RelayerKeyPassword() + rk, err := keys.LoadRelayerKey(app.Config().GetRelayerKeyPath(), chain.RawChain().Network, password) + if err != nil { + if errors.Is(err, keys.ErrRelayerKeyNotFound) || os.IsNotExist(err) { + // optional: proceed without relayer key + } else { + return nil, errors.Wrap(err, "unable to load relayer key") + } + } else { + relayerKey = rk + }Additionally add the missing import:
// add to imports import "os"zetaclient/chains/solana/signer/execute_spl.go (1)
143-154: Avoid panics: replace MustPublicKeyFromBase58 with safe parse and propagate error
sol.MustPublicKeyFromBase58will panic on malformed input; this is in the hot path of instruction creation. Parse and return an error instead.Apply:
- serializedInst, err := borsh.Serialize(contracts.ExecuteSPLRevertInstructionParams{ + revertSender, err := sol.PublicKeyFromBase58(msg.Sender()) + if err != nil { + return nil, errors.Wrapf(err, "invalid Solana sender %s", msg.Sender()) + } + serializedInst, err := borsh.Serialize(contracts.ExecuteSPLRevertInstructionParams{ Discriminator: contracts.DiscriminatorExecuteSPLRevert, Decimals: msg.Decimals(), Amount: msg.Amount(), - Sender: sol.MustPublicKeyFromBase58(msg.Sender()), + Sender: revertSender, Data: msg.Data(), Signature: msg.SigRS(), RecoveryID: msg.SigV(), MessageHash: msg.Hash(), Nonce: msg.Nonce(), })zetaclient/chains/solana/signer/execute.go (1)
59-64: Validate amount fits in uint64 to avoid silent truncationparams.Amount.Uint64() will truncate if the underlying value exceeds uint64, producing incorrect lamports. Guard explicitly.
- amount := params.Amount.Uint64() + if !params.Amount.IsUint64() { + return nil, nil, errors.Errorf("amount does not fit into uint64: %s", params.Amount.String()) + } + amount := params.Amount.Uint64()zetaclient/chains/solana/repo/repo.go (1)
176-190: Nil-check blockTime to avoid potential panicGetBlockTime can return a nil pointer when the cluster returns null. Calling Time() on nil will panic.
- blockTime, err := repo.solanaClient.GetBlockTime(ctx, slot) + blockTime, err := repo.solanaClient.GetBlockTime(ctx, slot) if err != nil { return time.Time{}, errors.Wrap(err, "unable to get latest block time") } + if blockTime == nil { + return time.Time{}, errors.New("latest block time is nil") + } return blockTime.Time(), nilzetaclient/chains/evm/observer/observer.go (1)
176-186: Compile-time bug: slice index must be int, not uintreceipt.TransactionIndex is uint; indexing a slice with it fails to compile. Cast once and reuse.
- if receipt.TransactionIndex >= uint(len(block.Transactions)) { - return fmt.Errorf("transaction index %d out of range [0, %d), txHash %s nonce %d block %d", - receipt.TransactionIndex, len(block.Transactions), tx.Hash(), tx.Nonce(), receipt.BlockNumber.Uint64()) - } - - txAtIndex := block.Transactions[receipt.TransactionIndex] + idx := int(receipt.TransactionIndex) + if idx >= len(block.Transactions) { + return fmt.Errorf("transaction index %d out of range [0, %d), txHash %s nonce %d block %d", + idx, len(block.Transactions), tx.Hash(), tx.Nonce(), receipt.BlockNumber.Uint64()) + } + + txAtIndex := block.Transactions[idx]
🧹 Nitpick comments (43)
zetaclient/chains/bitcoin/signer/signer_keysign_test.go (1)
59-63: Strengthen the test by verifying the TSS-signed tx.After fixing the sighash, validate the witness using the script engine (StandardVerifyFlags) rather than only logging, to assert correctness independent of deterministic k/equality with wallet signature.
zetaclient/chains/ton/observer/outbound.go (1)
55-58: Switch to GetOutboundTrackers: confirm ordering semantics.The prior call accepted an explicit Ascending order. Please confirm the new API returns a deterministic order (or sort by nonce locally) to avoid reprocessing/skips on restarts.
zetaclient/chains/solana/client.go (1)
12-16: No signature conflicts detected.
- Verified that
GetTransaction,GetAccountInfo,GetSlot, andGetSignaturesForAddressWithOptsshare identical signatures acrossobserver.SolanaClient,signer.SolanaClient, andrepo.SolanaClient.- Optional: Consolidate the
Clientinterface to embed onlyrepo.SolanaClient(per TODO, see issue #4224) to eliminate redundancy.zetaclient/chains/bitcoin/client.go (1)
9-13: Unified Bitcoin client interface: LGTM; add doc/mocks to cement the contractThe composition is clean and expresses intent well.
Consider adding a short package doc for the exported type and a mock generator directive to ease testing:
+// Client aggregates Bitcoin capabilities required by common, signer, and observer components. type Client interface { common.BitcoinClient signer.BitcoinClient observer.BitcoinClient }zetaclient/chains/sui/observer/inbound.go (1)
126-134: Preserve original RPC error while keeping the sentinel for flow controlWrap errTxNotFound with contextual details so the underlying RPC error isn't lost.
- txFresh, err := ob.suiClient.SuiGetTransactionBlock(ctx, txReq) + txFresh, err := ob.suiClient.SuiGetTransactionBlock(ctx, txReq) if err != nil { - return errors.Wrap(errTxNotFound, err.Error()) + return errors.Wrapf(errTxNotFound, "get tx %s: %v", event.TxHash, err) }zetaclient/orchestrator/preflight_metrics_reporter.go (4)
61-66: Per-chain elapsed time measurement is off; start timer inside the looptime.Since(start) currently measures from before the loop, not per chain.
- // perform preflight check - start := time.Now() - for _, chain := range unsupportedChains { + // perform preflight check + for _, chain := range unsupportedChains { + start := time.Now() switch { ... } if err != nil { logger.Error(). Err(err). Int64(logs.FieldChain, chain.ChainId). - Float64("time_taken", time.Since(start).Seconds()). + Float64("time_taken", time.Since(start).Seconds()). Msg("unable to report preflight metrics") } }Also applies to: 79-85
147-150: Inject solana repo/client via dependencies; minor TODO typoPrefer DI over inline construction to improve testability, and fix the comment typo.
- // TODO: The Solana repositorty should be injected as a dependency into this function. We - // should not have to instantiate the Solana client here. + // TODO: The Solana repository should be injected as a dependency into this function. + // We should not have to instantiate the Solana client here.
136-156: Bound Solana preflight RPC with a per-call timeoutAvoid indefinite waits on flaky endpoints.
func reportPreflightMetricsSolana(ctx context.Context, app *zctx.AppContext, chain chains.Chain) error { ... - blockTime, err := solrepo.New(rpcClient).HealthCheck(ctx) + localCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + blockTime, err := solrepo.New(rpcClient).HealthCheck(localCtx) if err != nil { return errors.Wrap(err, "unable to get solana last block time") }
176-191: Likewise, bound Sui preflight RPC with a timeoutMirror the same defensive timeout for Sui.
func reportPreflightMetricsSui(ctx context.Context, app *zctx.AppContext, chain chains.Chain) error { ... - suiClient := suiclient.New(cfg.Endpoint) + suiClient := suiclient.New(cfg.Endpoint) + localCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() - blockTime, err := suiClient.HealthCheck(ctx) + blockTime, err := suiClient.HealthCheck(localCtx) if err != nil { return errors.Wrap(err, "unable to get sui last block time") }zetaclient/chains/solana/signer/outbound_tracker_reporter.go (1)
50-60: Honor context cancellation inside the polling loopExit promptly when ctx is canceled to avoid orphan goroutines.
start := time.Now() for { + select { + case <-ctx.Done(): + logger.Info().Msg("context canceled; stopping tracker reporter") + return nil + default: + } // Solana block time is 0.4~0.8 seconds; wait 5 seconds between each check time.Sleep(5 * time.Second)zetaclient/chains/solana/signer/increment_nonce.go (1)
95-123: Instruction construction with sol alias: LGTM; consider returning the interface typeCurrent return type is *sol.GenericInstruction. If signTx accepts the sol.Instruction interface, prefer returning sol.Instruction to decouple from the concrete type. If signTx requires *sol.GenericInstruction, keep as-is.
Would you like me to draft a follow-up that updates signTx and call sites to accept sol.Instruction if feasible?
zetaclient/chains/solana/signer/signer_test.go (1)
54-88: Explicitly set the chain in every test caseThe second, third, and fourth table entries leave
chainat its zero value, sobase.NewSignerreceives achains.Chain{}withChainId == 0. That currently works because the constructor only decorates log fields, but it obscures intent and will break ifbase.NewSignerstarts enforcing a non-empty chain (or if metrics rely on the chain metadata). Please initialize thechainfield for those cases to match the first entry so the wiring remains explicit and future-proof.{ name: "should create solana signer successfully without relayer key", + chain: chain, chainParams: *chainParams, solanaClient: nil, tssSigner: nil, relayerKey: nil, ts: nil, logger: base.DefaultLogger(), }, { name: "should fail to create solana signer with invalid gateway address", + chain: chain, chainParams: func() observertypes.ChainParams { cp := *chainParams cp.GatewayAddress = "invalid" return cp }(), @@ { name: "should fail to create solana signer with invalid relayer key", + chain: chain, chainParams: *chainParams, solanaClient: nil, tssSigner: nil, relayerKey: &keys.RelayerKey{ PrivateKey: "3EMjCcCJg53fMEGVj13", // too shortzetaclient/chains/evm/client/client.go (1)
29-35: Dial options: HTTP client is ignored for ws:// endpointsrpc.WithHTTPClient applies to http(s) only. If any caller passes ws:// or wss://, dialing may fail or ignore the custom client. Either restrict endpoints to http(s) or branch on scheme.
zetaclient/chains/solana/observer/inbound.go (2)
32-38: Persist initial last scanned signatureYou set LastTxScanned in memory only. Consider persisting to DB so a restart doesn’t rescan from genesis for this address.
116-117: Update last scanned block even when processing signaturesYou only call WithLastBlockScanned when no new signatures are found. Consider updating it after the loop too (when errSlot==nil) to keep metrics fresh.
Additional snippet outside the hunk:
// after the for-loop, before return nil if errSlot == nil { ob.WithLastBlockScanned(lastSlot) }zetaclient/chains/solana/signer/withdraw_spl.go (2)
121-124: Reuse recipient ATA from message; avoid recomputationMsgWithdrawSPL already embeds recipient ATA; recomputing risks divergence and adds an RPC/CPU hit if helpers evolve.
Apply this diff:
- recipientAta, _, err := sol.FindAssociatedTokenAddress(msg.To(), msg.MintAccount()) - if err != nil { - return nil, errors.Wrapf(err, "cannot find ATA for %s and mint account %s", msg.To(), msg.MintAccount()) - } + recipientAta := msg.RecipientAta()
37-51: Fallback path coverageprepareWithdrawSPLTx uses signMsgWithFallback and createOutboundWithFallback. Consider adding a unit test that exercises the fallback broadcasting path for SPL withdraws. Based on learnings.
zetaclient/orchestrator/bootstrap.go (2)
167-168: Minor: rename alias for clarity.Consider renaming
solbservertosolobserver(or simplysolobserver) to avoid confusion at call sites. Behavior is unchanged.
161-166: Instrument Solana RPC client with timeouts/metrics via jsonrpc.NewClientWithOpts and rpc.NewWithCustomRPCClienthttpClient, err := metrics.GetInstrumentedHTTPClient(cfg.Endpoint) if err != nil { return nil, errors.Wrap(err, "unable to create instrumented HTTP client") } // create a jsonrpc client with your custom HTTP client jsonrpcClient, err := jsonrpc.NewClientWithOpts( cfg.Endpoint, jsonrpc.RPCClientOpts{HTTPClient: httpClient}, ) if err != nil { return nil, errors.Wrap(err, "unable to initialize JSON-RPC client") } // pass the instrumented jsonrpc client into the Solana RPC wrapper standardSolanaClient := rpc.NewWithCustomRPCClient(jsonrpcClient) if standardSolanaClient == nil { return nil, errors.New("unable to create Solana RPC client") }zetaclient/chains/bitcoin/observer/observer_test.go (1)
110-116: Fix typo in test name.“env var us invalid” → “env var is invalid”.
- name: "should fail if env var us invalid", + name: "should fail if env var is invalid",zetaclient/chains/bitcoin/signer/signer.go (1)
67-71: Lower log level for raw transaction payload.Hex-encoding entire tx at Info may be noisy; Debug is sufficient while preserving observability.
- signer.Logger().Std.Info(). + signer.Logger().Std.Debug(). Stringer(logs.FieldTx, signedTx.TxHash()). Str("signer_tx_payload", hex.EncodeToString(outBuff.Bytes())). Msg("broadcasting transaction")zetaclient/chains/solana/signer/execute_spl.go (1)
111-117: Prefer sol.Meta builder for consistency and clarity when mapping remaining accountsConstructing
*sol.AccountMetamanually is correct but inconsistent with the rest of the file and easy to get flags wrong. Usesol.Meta(pk)and chain.WRITE()when needed.-remainingAccounts := []*sol.AccountMeta{} -for _, a := range msg.Accounts { - remainingAccounts = append(remainingAccounts, &sol.AccountMeta{ - PublicKey: sol.PublicKey(a.PublicKey), - IsWritable: a.IsWritable, - }) -} +remainingAccounts := make([]*sol.AccountMeta, 0, len(msg.Accounts)) +for _, a := range msg.Accounts { + meta := sol.Meta(sol.PublicKey(a.PublicKey)) + if a.IsWritable { + meta = meta.WRITE() + } + remainingAccounts = append(remainingAccounts, meta) +}zetaclient/chains/solana/observer/observer_test.go (2)
19-27: Add t.Helper() to improve test failure reportingMark the factory as a helper to attribute assertion failures to the calling test.
func MockSolanaObserver( t *testing.T, chain chains.Chain, solanaClient observer.SolanaClient, chainParams observertypes.ChainParams, zetacoreClient interfaces.ZetacoreClient, tssSigner interfaces.TSSSigner, ) *observer.Observer { + t.Helper()
41-51: Resource hygiene: close in-memory DBEven for in-memory DBs, close handles to avoid descriptor leaks when tests scale.
database, err := db.NewFromSqliteInMemory(true) require.NoError(t, err) + t.Cleanup(func() { _ = database.Close() })zetaclient/chains/bitcoin/common/fee.go (2)
60-66: Avoid interface duplication across packagesThis
BitcoinClientpartially overlaps withobserver.BitcoinClient(broader surface). Divergent interfaces hinder reuse and mocks.
- Extract a shared
zetaclient/chains/bitcoin/clientpackage with cohesive sub-interfaces (Core, Mempool, Blocks, Tx) and consume those here and in observers.- Alternatively, import the existing broader interface where feasible to avoid a second name for the same concept.
264-309: Protect against underflow near chain genesis when looking back blocksOn very short chains (test/dev),
blockNumber-ican go negative. Clamp the start height.- highestRate := int64(0) - for i := int64(0); i < feeRateCountBackBlocks; i++ { - // get the block - hash, err := bitcoinClient.GetBlockHash(ctx, blockNumber-i) + highestRate := int64(0) + start := blockNumber - feeRateCountBackBlocks + 1 + if start < 0 { + start = 0 + } + for h := blockNumber; h >= start; h-- { + hash, err := bitcoinClient.GetBlockHash(ctx, h)zetaclient/chains/solana/signer/execute.go (1)
112-119: Avoid MustPublicKeyFromBase58; handle parse errors insteadUsing MustPublicKeyFromBase58 can panic on unexpected inputs (e.g., future call sites). You already validate the sender earlier; still, prefer explicit error handling here.
- serializedInst, err := borsh.Serialize(contracts.ExecuteRevertInstructionParams{ - Discriminator: discriminator, - Amount: msg.Amount(), - Sender: sol.MustPublicKeyFromBase58(msg.Sender()), + senderPK, err := sol.PublicKeyFromBase58(msg.Sender()) + if err != nil { + return nil, errors.Wrap(err, "invalid sender pubkey for revert execute") + } + serializedInst, err := borsh.Serialize(contracts.ExecuteRevertInstructionParams{ + Discriminator: discriminator, + Amount: msg.Amount(), + Sender: senderPK,zetaclient/chains/solana/repo/repo_test.go (2)
45-48: Avoid shadowing the imported repo package with a local variablerepo := repo.New(client) shadows the imported package name and hurts readability. Prefer solRepo or r.
- repo := repo.New(client) + solRepo := repo.New(client)Apply similarly in all helper functions.
Also applies to: 65-68, 83-86, 108-111, 126-129
76-79: Reduce flakiness from hardcoded first signatureDevnet history can reset; asserting the exact first signature is brittle. Assert non-emptiness or properties instead.
- actualSig := "2tUQtcrXxtNFtV9kZ4kQsmY7snnEoEEArmu9pUptr4UCy8UdbtjPD6UtfEtPJ2qk5CTzZTmLwsbmZdLymcwSUcHu" - require.Equal(t, actualSig, sig.String()) + require.NotEmpty(t, sig.String())zetaclient/chains/solana/repo/repo.go (3)
155-173: Use typed RPCError instead of string matchingRelying on strings.Contains for "-32015" is brittle. Prefer a type assertion to *solrpc.RPCError and check Code.
- switch { - case err != nil && strings.Contains(err.Error(), errorCodeUnsupportedTransactionVersion): - return nil, ErrUnsupportedTxVersion - case err != nil: - return nil, err - default: - return txResult, nil - } + if err != nil { + var rpcErr *solrpc.RPCError + if errors.As(err, &rpcErr) && rpcErr.Code == -32015 { + return nil, ErrUnsupportedTxVersion + } + return nil, err + } + return txResult, nil
60-101: Default pageLimit when unset and bound itGuard against callers passing 0/negative pageLimit; also cap to a sane upper bound if desired.
func (repo SolanaRepo) GetFirstSignatureForAddress(ctx context.Context, address sol.PublicKey, pageLimit int, ) (sol.Signature, error) { + if pageLimit <= 0 { + pageLimit = DefaultPageLimit + }Apply similar handling in GetSignaturesForAddressUntil.
21-26: Unexport DefaultPageLimit or use it internallyThe TODO notes this constant need not be exported. Either unexport it (defaultPageLimit) or start using it when pageLimit ≤ 0 as suggested above.
zetaclient/chains/sui/observer/observer.go (1)
34-45: Consider centralizing the Sui client contractWe now have an observer-scoped
SuiClientalongside the signer variant, each carrying a slightly different method set. Keeping these interfaces in-package risks drift the next time we add or rename a method. Please consider moving a single shared definition into thechains/sui/clientpackage (or another common location) and importing it here to keep the contract authoritative.zetaclient/chains/sui/signer/signer.go (1)
32-68: Unify the Sui client interface definitionThe signer now owns its own
SuiClientinterface while the observer keeps a parallel one; both describe the same concrete client but expose different subsets of methods. To avoid divergent contracts and redundant mock maintenance, please consolidate these into a single shared interface (for example inchains/sui/client) and have both signer/observer depend on that canonical type.zetaclient/chains/evm/signer/signer.go (4)
71-86: Constructor should validate evmClient is non-nilPrevent nil deref later by validating the dependency at construction time.
func New( baseSigner *base.Signer, - evmClient EVMClient, + evmClient EVMClient, zetaConnectorAddress ethcommon.Address, erc20CustodyAddress ethcommon.Address, gatewayAddress ethcommon.Address, ) (*Signer, error) { - return &Signer{ + if evmClient == nil { + return nil, errors.New("evm client is nil") + } + return &Signer{ Signer: baseSigner, evmClient: evmClient, zetaConnectorAddress: zetaConnectorAddress, er20CustodyAddress: erc20CustodyAddress, gatewayAddress: gatewayAddress, }, nil }
355-365: Use SplitN to avoid truncating params containing ':'Ensures admin command params can include colons.
- msg := strings.Split(cctx.RelayedMessage, ":") - if len(msg) != 2 { + msg := strings.SplitN(cctx.RelayedMessage, ":", 2) + if len(msg) < 2 { return nil, fmt.Errorf("invalid message %s", msg) }
447-454: Add timeout to NonceAt to avoid hanging retriesWrap the nonce query with the same broadcast timeout to bound each attempt.
- latestNonce, err := signer.evmClient.NonceAt(ctx, signer.TSS().PubKey().AddressEVM(), nil) + ctxNonce, cancelNonce := context.WithTimeout(ctx, broadcastTimeout) + defer cancelNonce() + latestNonce, err := signer.evmClient.NonceAt(ctxNonce, signer.TSS().PubKey().AddressEVM(), nil)
193-201: Parameter ‘chainID’ is unused in newTxEither use it (re-enable EIP‑1559 path) or drop it to reduce confusion.
Would you like me to provide a minimal diff to remove the param from newTx and its callers, or to restore the dynamic-fee branch behind gas.isLegacy?
zetaclient/chains/ton/observer/observer.go (1)
125-135: Consistent error context in CheckRPCStatusMessage says “TON client health” elsewhere; consider aligning wording for log/search consistency.
- return errors.Wrap(err, "unable to check TON client health") + return errors.Wrap(err, "unable to check TON client health")Note: If you prefer “RPC health”, update related logs accordingly for consistency across chains.
zetaclient/chains/evm/observer/observer.go (2)
85-102: Constructor should validate evmClient is non-nilGuard against nil dependencies to avoid runtime panics.
-func New(baseObserver *base.Observer, evmClient EVMClient) (*Observer, error) { +func New(baseObserver *base.Observer, evmClient EVMClient) (*Observer, error) { + if evmClient == nil { + return nil, errors.New("evm client is nil") + } // create evm observer ob := &Observer{ Observer: baseObserver, evmClient: evmClient, outboundConfirmedReceipts: make(map[string]*eth.Receipt), outboundConfirmedTransactions: make(map[string]*eth.Transaction), priorityFeeConfig: priorityFeeConfig{}, }
141-147: Minor: avoid double OutboundID computation under lockCompute once to reduce work while holding the mutex.
func (ob *Observer) setTxNReceipt(nonce uint64, receipt *eth.Receipt, transaction *eth.Transaction) { ob.Mu().Lock() defer ob.Mu().Unlock() - ob.outboundConfirmedReceipts[ob.OutboundID(nonce)] = receipt - ob.outboundConfirmedTransactions[ob.OutboundID(nonce)] = transaction + id := ob.OutboundID(nonce) + ob.outboundConfirmedReceipts[id] = receipt + ob.outboundConfirmedTransactions[id] = transaction }zetaclient/chains/solana/observer/observer.go (1)
17-67: Unify the SolanaClient interface in a single packageWe now have exported
SolanaClientinterfaces inobserver,signer, andrepo, each with a slightly different method set. This duplication is brittle—every signature tweak has to be synchronized across packages, and it only takes one missed update for the concrete client to stop compiling or to hide capability mismatches at runtime. Please move the interface to a shared location (for example, a dedicatedchains/solana/interfacespackage) and have all consumers import that single definition. It keeps the contract authoritative and avoids future drift.zetaclient/chains/solana/signer/signer.go (1)
51-79: Reuse the SolanaClient interface defined for the observer/repoThe signer declares yet another exported
SolanaClientinterface, overlapping—but not identical—with the versions inobserverandrepo. This fragmentation guarantees drift as soon as we add or rename a method. Please factor the interface into a common package (or reuse the one introduced for the observer) so every Solana component depends on the same contract. It simplifies mocking and keeps the API surface coherent.
…r-signers for dry-mode (#4213) refactor: prepare the client interfaces of the observer-signers for dry-mode
a2f0013 to
1388bca
Compare
Description
Backports #4213.
Note
Replaces the shared interfaces with per-chain clients (BTC/EVM/Solana/Sui/TON) and a Solana repo, updates observers/signers/orchestrator and mocks, and unifies outbound tracker retrieval.
zetaclient/chains/interfaces; introduce per-chain clients (bitcoin,evm,solana,sui,ton) andinterfaces/{tss,zetacore}.tss→tssSigner; EVM clientSigner()accessor).GetOutboundTrackers(remove ordered variant) and update callers.solana/repoabstraction; route reads (e.g.,GetTransaction, signatures, healthcheck) through repo.SolanaClient+ repo; adjust instruction/account types.RPCwithBitcoinClientacross common/observer/signer; adapt fee, mempool, and block queries.EVMClientinterfaces; adjust tx signing and nonce fetching.Written by Cursor Bugbot for commit a2f0013. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
Refactor
Documentation
Chores